﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
using Addins.UI.ViewModel;
using Raona.Code.Inspection;
using WcfClientGenerator.Model.Config;
using WcfClientGenerator.UI.Command;
using WcfClientGenerator.UI.Resx;
using WcfClientGenerator.UI.View;

namespace WcfClientGenerator.UI.ViewModel
{
    public class ConfigurationsViewModel : BaseViewModel, IRequestCloseViewModel
    {
        #region Instance Fields (Private)

        /// <summary>
        /// The addin configuration
        /// </summary>
        private WcfClientGeneratorConfig configuration = null;

        /// <summary>
        /// The available configurations
        /// </summary>
        private ObservableCollection<WcfClientConfiguration> configurations = null;

        /// <summary>
        /// The currently selected configuration
        /// </summary>
        private WcfClientConfiguration selectedConfiguration = null;

        /// <summary>
        /// The current solution object
        /// </summary>
        private EnvDTE80.DTE2 applicationObject = null;

        /// <summary>
        /// The command invoked when the user wants to generate proxies for the selected configuration
        /// </summary>
        public DelegatingCommand generateCommand = null;

        /// <summary>
        /// The command invoked when the user needs to edit the configuration
        /// </summary>
        public DelegatingCommand editConfigurationCommand = null;

        /// <summary>
        /// The command invoked when the user wants to close the main window
        /// </summary>
        public DelegatingCommand closeCommand = null;

        /// <summary>
        /// The window hosting the addin UI
        /// </summary>
        private Window ownerWindow = null;

        /// <summary>
        /// The message to show to the user if the configuration validation fails
        /// </summary>
        private string validationMessage = null;

        /// <summary>
        /// The project inspector helper for project validation
        /// </summary>
        private ProjectInspector projectInspector = null;

        /// <summary>
        /// true if the view has already called the close command
        /// </summary>
        private bool isClosed = false;

        #endregion

        #region Events (Public)

        /// <summary>
        /// Raised when the associated view should be dismissed
        /// </summary>
        public event EventHandler RequestClose;

        #endregion

        #region Instance Constructors (Public)

        public ConfigurationsViewModel(WcfClientGeneratorConfig configuration, EnvDTE80.DTE2 applicationObject, Window ownerWindow)
        {
            this.ownerWindow = ownerWindow;
            this.applicationObject = applicationObject;
            this.projectInspector = new ProjectInspector(applicationObject);
            this.Configuration = configuration;
            this.ChangeSelectedConfiguration();
        }

        #endregion

        #region Instance Properties (Public)

        public WcfClientGeneratorConfig Configuration
        {
            get
            {
                return this.configuration;
            }
            set
            {
                if (this.configuration != null)
                {
                    this.configuration.PropertyChanged -= new System.ComponentModel.PropertyChangedEventHandler(this.OnConfigurationPropertyChanged);
                }
                this.configuration = value;
                if (this.configuration != null)
                {
                    this.configuration.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(this.OnConfigurationPropertyChanged);
                }

                this.NotifyPropertyChanged("ClientConfigurations");
            }
        }

        public List<WcfClientConfiguration> ClientConfigurations
        {
            get
            {
                if (this.configuration != null)
                    return this.configuration.ClientConfigurations;
                else
                    return null;
            }
        }
        /// <summary>
        /// The currently selected configuration
        /// </summary>
        public WcfClientConfiguration SelectedConfiguration
        {
            get
            {
                return this.selectedConfiguration;
            }
            set
            {
                WcfClientConfiguration oldSelectedConfiguration = this.selectedConfiguration;
                this.selectedConfiguration = value;
                if (oldSelectedConfiguration != this.selectedConfiguration)
                    this.NotifyPropertyChanged("SelectedConfiguration");
            }
        }

        /// <summary>
        /// The message to show to the user
        /// </summary>
        public string ValidationMessage
        {
            get
            {
                return this.validationMessage;
            }
            set
            {
                string oldMessage = this.validationMessage;
                this.validationMessage = value;
                if (this.validationMessage != oldMessage)
                    this.NotifyPropertyChanged("ValidationMessage");
            }
        }

        /// <summary>
        /// The collection of projects in the current solution
        /// </summary>
        public EnvDTE80.DTE2 ApplicationObject
        {
            get
            {
                return this.applicationObject;
            }
            set
            {
                this.applicationObject = value;
            }
        }

        /// <summary>
        /// The command invoked when the user needs to edit the configuration
        /// </summary>
        public ICommand EditConfigurationCommand
        {
            get
            {
                if (this.editConfigurationCommand == null)
                {
                    this.editConfigurationCommand = new DelegatingCommand(
                        () =>
                        {
                            this.ValidationMessage = null;
                            EditConfigurationsViewModel viewModel = new EditConfigurationsViewModel(WcfClientGeneratorConfig.Instance, this.applicationObject);
                            EditConfigurationsView view = new EditConfigurationsView { DataContext = viewModel };
                            viewModel.ConfigurationChanged += (sender, e) =>
                            {
                                WcfClientGeneratorConfig.Save(e.WcfClientGeneratorConfig);
                                this.Configuration = e.WcfClientGeneratorConfig;
                                this.ChangeSelectedConfiguration();
                            };
                            view.ShowDialog();
                        },
                        () =>
                        {
                            return true;
                        });
                }
                return this.editConfigurationCommand;
            }
        }

        /// <summary>
        /// The command invoked when the user wants to close the main window
        /// </summary>
        public ICommand CloseCommand
        {
            get
            {
                if (this.closeCommand == null)
                {
                    this.closeCommand = new DelegatingCommand(
                        () =>
                        {
                            if (!this.isClosed)
                            {
                                WcfClientGeneratorConfig.Save(this.Configuration);
                                this.Close();
                                this.isClosed = true;
                            }
                        },
                        () =>
                        {
                            return true;
                        });
                }
                return this.closeCommand;
            }
        }

        public ICommand GenerateCommand
        {
            get
            {
                if (this.generateCommand == null)
                {
                    this.generateCommand = new DelegatingCommand(
                        () =>
                        {
                            if (this.SelectedConfiguration != null)
                            {
                                try
                                {
                                    CodeGenerationWindow view = new CodeGenerationWindow(this.SelectedConfiguration, this.applicationObject);
                                    view.Owner = this.ownerWindow;
                                    bool? result = view.ShowDialog();
                                }
                                catch (Exception ex)
                                {
                                    MessageBox.Show(ex.Message, AppResources.mainWindowTitle);
                                }
                            }
                        },
                        () =>
                        {
                            return this.ValidateConfiguration(this.SelectedConfiguration);
                        });
                }
                return this.generateCommand;
            }
        }

        #endregion

        #region Instance Methods (Private)

        private void Close()
        {
            if (this.RequestClose != null)
            {
                this.RequestClose(this, EventArgs.Empty);
            }
        }


        private void ChangeSelectedConfiguration()
        {
            if (this.configuration != null
                && this.configuration.ClientConfigurations != null
                && this.configuration.ClientConfigurations.Count > 0
                && this.configuration.CurrentConfiguration >= 0
                && this.configuration.CurrentConfiguration < this.configuration.ClientConfigurations.Count)
                this.SelectedConfiguration = this.configuration.ClientConfigurations[this.configuration.CurrentConfiguration];
            else
                this.SelectedConfiguration = null;
        }

        private bool ValidateConfiguration(WcfClientConfiguration clientConfiguration)
        {
            bool isValid = true;
            this.ValidationMessage = null;
            StringBuilder messageBuilder = new StringBuilder();

            // There must be a currently selected configuration
            if (clientConfiguration != null)
            {
                // The contracts project must be in the current solution
                if (!string.IsNullOrEmpty(clientConfiguration.ContractsProjectName) &&
                    this.ProjectExists(clientConfiguration.ContractsProjectName))
                {
                    if (this.ProjectHasOutput(clientConfiguration.ContractsProjectName))
                    {
                        // There must be at least one target project
                        if (this.HasAsyncDestinationProjects(clientConfiguration)
                            ||
                            this.HasSyncDestinationProjects(clientConfiguration))
                        {
                            // Check that all async destination projects exist in the solution
                            List<string> projectList = new List<string>();
                            if (this.HasAsyncDestinationProjects(clientConfiguration))
                                projectList.AddRange(clientConfiguration.AsyncDestinationProjects
                                    .Where(name => this.projectInspector.FindProject(name) == null));
                            // Check that all sync destination projects exist in the solution
                            if (this.HasSyncDestinationProjects(clientConfiguration))
                                projectList.AddRange(clientConfiguration.SyncDestinationProjects
                                    .Where(name => this.projectInspector.FindProject(name) == null));
                            if (projectList.Count == 0)
                            {
                                // Check the sync proxies and contracts file names have been provided
                                if (this.HasSyncDestinationProjects(clientConfiguration)
                                    && string.IsNullOrWhiteSpace(clientConfiguration.SyncContractsFileName))
                                    messageBuilder.AppendLine(AppResources.noSyncContractsFileName);
                                if (this.HasSyncDestinationProjects(clientConfiguration)
                                    && string.IsNullOrWhiteSpace(clientConfiguration.SyncProxiesFileName))
                                    messageBuilder.AppendLine(AppResources.noSyncProxiesFileName);
                                // Check the async proxies and contracts file names have been provided
                                if (this.HasSyncDestinationProjects(clientConfiguration)
                                    && string.IsNullOrWhiteSpace(clientConfiguration.AsyncContractsFileName))
                                    messageBuilder.AppendLine(AppResources.noAsyncContractsFileName);
                                if (this.HasSyncDestinationProjects(clientConfiguration)
                                    && string.IsNullOrWhiteSpace(clientConfiguration.AsyncProxiesFileName))
                                    messageBuilder.AppendLine(AppResources.noAsyncProxiesFileName);
                                // Check that a namespace has been supplied
                                if (string.IsNullOrWhiteSpace(clientConfiguration.ServiceContractsNamespace))
                                    messageBuilder.AppendLine(AppResources.noServiceContractsNamespace);
                                // Check the templates code base has been supplied
                                if (string.IsNullOrWhiteSpace(clientConfiguration.TemplatesAssemblyCodeBase))
                                    messageBuilder.AppendLine(AppResources.noTemplatesAssembly);
                                else
                                {
                                    // Check the templates code base is well-formed
                                    string escapedCodeBase = Uri.EscapeUriString(clientConfiguration.TemplatesAssemblyCodeBase);
                                    if (!Uri.IsWellFormedUriString(escapedCodeBase, UriKind.RelativeOrAbsolute))
                                        messageBuilder.AppendLine(AppResources.invalidTemplatesCodebase);
                                }
                                // Check a client base class has been supplied
                                if (string.IsNullOrWhiteSpace(clientConfiguration.ClientBaseName))
                                    messageBuilder.AppendLine(AppResources.invalidTemplatesCodebase);
                                // Check the temporary folder is valid
                                if (string.IsNullOrWhiteSpace(clientConfiguration.GenerationFolder)
                                    || !System.IO.Directory.Exists(clientConfiguration.GenerationFolder))
                                    messageBuilder.AppendLine(AppResources.invalidGenerationFolder);
                                // If remote generation is specified, validate service URL
                                if (clientConfiguration.UseGeneratorService
                                    && (string.IsNullOrEmpty(clientConfiguration.GeneratorServiceUrl)
                                        || !Uri.IsWellFormedUriString(clientConfiguration.GeneratorServiceUrl, UriKind.Absolute)))
                                    messageBuilder.AppendLine(AppResources.invalidGeneratorServiceUrlMessage);

                                isValid = messageBuilder.Length == 0;
                                if (!isValid)
                                    this.ValidationMessage = messageBuilder.ToString();
                            }
                            else
                            {
                                StringBuilder builder = new StringBuilder();
                                foreach (string name in projectList)
                                {
                                    builder.AppendFormat(AppResources.projectNotFound, name);
                                    builder.AppendLine();
                                }
                                this.ValidationMessage = builder.ToString();
                            }
                        }
                        else
                        {
                            this.ValidationMessage = AppResources.noTargetProjectSelected;
                        }
                    }
                    else
                    {
                        this.ValidationMessage = AppResources.contractsProjectOutputMissing;
                    }
                }
                else
                {
                    this.ValidationMessage = AppResources.noContractsProject;
                }
            }
            else
            {
                this.ValidationMessage = AppResources.noConfigurationSelected;
            }
            return isValid;
        }

        private bool ProjectHasOutput(string projectName)
        {
            return this.projectInspector.HasOutputFile(projectName);
        }

        private bool HasSyncDestinationProjects(WcfClientConfiguration clientConfiguration)
        {
            return clientConfiguration.SyncDestinationProjects != null
                   && clientConfiguration.SyncDestinationProjects.Count > 0;
        }

        private bool HasAsyncDestinationProjects(WcfClientConfiguration clientConfiguration)
        {
            return clientConfiguration.AsyncDestinationProjects != null
                   && clientConfiguration.AsyncDestinationProjects.Count > 0;
        }

        private bool ProjectExists(string projectName)
        {
            return this.projectInspector.FindProject(projectName) != null;
        }

        #endregion

        #region Event Handlers (Private)

        private void OnConfigurationPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "CurrentConfiguration")
            {
                this.ChangeSelectedConfiguration();
            }
        }

        #endregion
    }
}
